package com.pactera.rabbitmq.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.pactera.rabbitmq.autoconfigure.RabbitmqProperties;
import com.pactera.rabbitmq.constants.ExchageType;
import com.pactera.rabbitmq.constants.Role;
import com.pactera.rabbitmq.exception.RabbitException;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.http.*;
import org.springframework.util.Base64Utils;
import org.springframework.web.client.RestTemplate;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 管理员工具类
 *
 * @author: liqingdian
 */
public class AdminUtils {
    private static final Logger log = LoggerFactory.getLogger(AdminUtils.class);

    private AdminUtils() {
    }

    private static RestTemplate restTemplate;
    private static RabbitmqProperties.Management management;

    public static AdminUtils builder(RestTemplate restTemplate, RabbitmqProperties.Management management) {
        AdminUtils.restTemplate = restTemplate;
        AdminUtils.management = management;
        return new AdminUtils();
    }

    public static RestTemplate restTemplate() {
        return restTemplate;
    }

    /**
     * 实时数据同步EXCHANGE-REAL-TIME-DATA-SYNC
     */
    public static final String EXCHANGE_RTDS = "EXCHANGE-R-T-D-S";
    /**
     * 实时数据同步QUEUE-REAL-TIME-DATA-SYNC
     */
    public static final String QUEUE_RTDS = "QUEUE-R-T-D-S";
    /**
     * 重试交互机/队列后缀
     */
    public static final String RETRY = "RETRY";
    /**
     * 失败交互机/队列后缀
     */
    public static final String FAILED = "FAILED";

    /**
     * 声明实时数据推送的配置
     *
     * @param username  用户名，亦作为队列名称
     * @param password  密码
     * @param maxLength 队列最大长度
     * @param ttl       消息存活时间(毫秒)
     * @param retryTime 重试时间(毫秒)
     * @author: liqingdian
     */
    public static void declareRtds(String username, String password, long maxLength, long ttl, long retryTime) {
        Assert.isNotBlank(username, "[username]不能为空");
        Assert.isNotBlank(password, "[password]不能为空");
        // 声明推送交换机、重试交换机、失败交换机
        RabbitUtils.exchangeDeclare(EXCHANGE_RTDS, ExchageType.DIRECT, true, false, null);
        RabbitUtils.exchangeDeclare(AdminUtils.retry(EXCHANGE_RTDS), ExchageType.DIRECT, true, false, null);
        RabbitUtils.exchangeDeclare(AdminUtils.failed(EXCHANGE_RTDS), ExchageType.DIRECT, true, false, null);

        // 声明消息队列、重试(死信)队列、失败队列
        Map<String, Object> failed = new HashMap<>();
        if (maxLength > 0) {
            failed.put("x-max-length", maxLength);
        } else {
            failed.put("x-max-length", Short.MAX_VALUE);
        }
        if (ttl > 0) {
            failed.put("x-message-ttl", ttl);
        } else {
            failed.put("x-message-ttl", 60 * 1000L);
        }
        failed.put("x-dead-letter-exchange", AdminUtils.failed(EXCHANGE_RTDS));
        failed.put("x-dead-letter-routing-key", AdminUtils.failed(QUEUE_RTDS));
        RabbitUtils.queueDeclare(username, true, false, false, failed);

        Map<String, Object> retryArguments = new HashMap<String, Object>();
        if (retryTime <= 0) {
            retryTime = 60 * 1000L;
        }
        retryArguments.put("x-message-ttl", retryTime);
        retryArguments.put("x-dead-letter-exchange", EXCHANGE_RTDS);
        retryArguments.put("x-dead-letter-routing-key", AdminUtils.retry(username));
        RabbitUtils.queueDeclare(retry(username), true, false, false, retryArguments);

        RabbitUtils.queueDeclare(AdminUtils.failed(QUEUE_RTDS), true, false, false, null);

        // 绑定交换机和队列:
        // EXCHANGE_RTDS-username-username
        RabbitUtils.bind(username, false, EXCHANGE_RTDS, username, null);
        // EXCHANGE_RTD-username@RETRY-username
        RabbitUtils.bind(username, false, EXCHANGE_RTDS, AdminUtils.retry(username), null);
        // EXCHANGE_RTD@RETRY-username@retry-username@retry
        RabbitUtils.bind(AdminUtils.retry(username), false, AdminUtils.retry(EXCHANGE_RTDS), AdminUtils.retry(username), null);
        // EXCHANGE_RTDS@FAILED-QUEUE_RTDS-QUEUE_RTDS
        RabbitUtils.bind(AdminUtils.failed(QUEUE_RTDS), false, AdminUtils.failed(EXCHANGE_RTDS), AdminUtils.failed(QUEUE_RTDS), null);

        // 声明用户、权限
        if (management.isEnabled()) {
            AdminUtils.saveUser(username, password, Role.IMPERSONATOR);
            ConnectionFactory connectionFactory = RabbitUtils.rabbitTemplate().getConnectionFactory();
            String virtualHost = connectionFactory.getVirtualHost();
            AdminUtils.savePermissions(
                    virtualHost,
                    username,
                    "^$",
                    Joiner.on("").join("^(", AdminUtils.retry(EXCHANGE_RTDS), "|", AdminUtils.failed(EXCHANGE_RTDS), ")$"),
                    Joiner.on("").join("^", username, "$")
            );
        }
        log.info("{}- 实时数据同步推送配置初始化完成", username);
    }

    /**
     * 拼接重试后缀
     *
     * @param name 名称
     * @author: liqingdian
     */
    public static String retry(String name) {
        return Joiner.on("@").join(name, RETRY);
    }

    /**
     * 拼接失败后缀
     *
     * @param name 名称
     * @author: liqingdian
     */
    public static String failed(String name) {
        return Joiner.on("@").join(name, FAILED);
    }

    /**
     * 获取请求实体
     *
     * @param param   请求参数json串
     * @param headers 请求头参数
     * @author: liqingdian
     */
    private static HttpEntity getEntity(String param, Map<String, String> headers) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        String username = management.getUsername();
        String password = management.getPassword();
        String usernamePassword = Joiner.on(":").join(username, password);
        String basic = Joiner.on("").join("Basic ", Base64Utils.encodeToString(usernamePassword.getBytes(StandardCharsets.UTF_8)));
        httpHeaders.set("Authorization", basic);
        httpHeaders.set("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36");
        httpHeaders.set("Accept-Encoding", "gzip,deflate");
        httpHeaders.set("Accept-Language", "zh-CN");
        httpHeaders.set("Connection", "Keep-Alive");
        if (MapUtils.isNotEmpty(headers)) {
            headers.forEach((a, b) -> httpHeaders.set(a, b));
        }
        HttpEntity entity;
        if (StringUtils.isNotBlank(param)) {
            entity = new HttpEntity(param, httpHeaders);
        } else {
            entity = new HttpEntity(httpHeaders);
        }
        return entity;
    }

    /**
     * 请求
     *
     * @param uri     请求uri
     * @param method  请求方式
     * @param param   请求参数
     * @param headers 请求头参数
     * @author: liqingdian
     */
    private static String reqeust(String uri, HttpMethod method, String param, Map<String, String> headers) {
        String url = Joiner.on("").join(management.getUrl(), uri);
        URI http;
        try {
            http = new URI(url);
        } catch (URISyntaxException e) {
            throw new RabbitException(e);
        }
        ResponseEntity<String> response = restTemplate.exchange(http, method, AdminUtils.getEntity(param, headers), String.class);
        if (response.getStatusCode().is2xxSuccessful()) {
            return response.getBody();
        }
        throw new RabbitException(response.getStatusCode().getReasonPhrase());
    }

    /**
     * 格式换uri地址
     *
     * @param format 格式
     * @param value  值
     * @author: liqingdian
     */
    private static String format(String format, String... value) {
        Object[] values = Lists.newArrayList(value).stream().map(a -> {
            try {
                return URLEncoder.encode(a, CharEncoding.UTF_8);
            } catch (UnsupportedEncodingException e) {
                log.error(e.getMessage(), e);
            }
            return a;
        }).toArray();
        return String.format(format, values);
    }

    /**
     * 将json串转对象
     *
     * @author: liqingdian
     */
    public static <T> T parseObject(String text, Class<T> clazz) {
        return JSON.parseObject(text, clazz);
    }

    /**
     * 将json串转列表
     *
     * @author: liqingdian
     */
    public static <T> List<T> parseArray(String text, Class<T> clazz) {
        return JSON.parseArray(text, clazz);
    }

    /**
     * 获取系统详情
     *
     * @author: liqingdian
     */
    public static String getInfo() {
        return AdminUtils.reqeust("/api/overview", HttpMethod.GET, null, null);
    }

    /**
     * 获取集群节点列表信息
     *
     * @author: liqingdian
     */
    public static String getNodes() {
        return AdminUtils.reqeust("/api/nodes", HttpMethod.GET, null, null);
    }

    /**
     * 获取指定节点节点列表信息
     *
     * @param node 节点名称
     * @author: liqingdian
     */
    public static String getNode(String node) {
        String format = AdminUtils.format("/api/nodes/%s", node);
        return AdminUtils.reqeust(String.format(format, node), HttpMethod.GET, null, null);
    }

    /**
     * 获取交换、队列、绑定、用户、虚拟主机、权限和参数定义信息
     *
     * @author: liqingdian
     */
    public static String getDefinitions() {
        return AdminUtils.reqeust("/api/definitions", HttpMethod.GET, null, null);
    }

    /**
     * 获取所有打开的连接的列表
     *
     * @author: liqingdian
     */
    public static String getConnections() {
        return AdminUtils.reqeust("/api/connections", HttpMethod.GET, null, null);
    }

    /**
     * 获取指定的连接的列表
     *
     * @param connection 连接名称
     * @author: liqingdian
     */
    public static String getConnection(String connection) {
        String format = AdminUtils.format("/api/connections/%s", connection);
        return AdminUtils.reqeust(String.format(format, connection), HttpMethod.GET, null, null);
    }

    /**
     * 关闭连接
     *
     * @param connection 连接名称
     * @param reason     关闭原因
     * @author: liqingdian
     */
    public static void closeConnection(String connection, String reason) {
        String format = AdminUtils.format("/api/connections/%s", connection);
        HashMap<String, String> headers = null;
        if (StringUtils.isNotBlank(reason)) {
            headers = Maps.newHashMap();
            headers.put("X-Reason", reason);
        }
        AdminUtils.reqeust(String.format(format, connection), HttpMethod.DELETE, null, headers);
    }

    /**
     * 获取指定的连接的信道列表
     *
     * @param connection 连接名称
     * @author: liqingdian
     */
    public static String getChannelsByConnection(String connection) {
        String format = AdminUtils.format("/api/connections/%s/channels", connection);
        return AdminUtils.reqeust(String.format(format, connection), HttpMethod.GET, null, null);
    }

    /**
     * 获取信道列表
     *
     * @author: liqingdian
     */
    public static String getChannels() {
        return AdminUtils.reqeust("/api/channels", HttpMethod.GET, null, null);
    }

    /**
     * 获取信道
     *
     * @param channel 信道名称
     * @author: liqingdian
     */
    public static String getChannel(String channel) {
        String format = AdminUtils.format("/api/channels/%s", channel);
        return AdminUtils.reqeust(String.format(format, channel), HttpMethod.GET, null, null);
    }

    /**
     * 获取消费者列表
     *
     * @author: liqingdian
     */
    public static String getSonsumers() {
        return AdminUtils.reqeust("/api/consumers", HttpMethod.GET, null, null);
    }

    /**
     * 获取消费者
     *
     * @param vhost 虚拟机名称
     * @author: liqingdian
     */
    public static String getSonsumers(String vhost) {
        String format = AdminUtils.format("/api/consumers/%s", vhost);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 获取交换机列表
     *
     * @author: liqingdian
     */
    public static String getExchanges() {
        return AdminUtils.reqeust("/api/exchanges", HttpMethod.GET, null, null);
    }

    /**
     * 获取交换机
     *
     * @param vhost 虚拟机名称
     * @author: liqingdian
     */
    public static String getExchanges(String vhost) {
        String format = AdminUtils.format("/api/exchanges/%s", vhost);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 获取交换机
     *
     * @param vhost    虚拟机名称
     * @param exchange 交换机名称
     * @author: liqingdian
     */
    public static String getExchange(String vhost, String exchange) {
        String format = AdminUtils.format("/api/exchanges/%s/%s", vhost, exchange);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 绑定源列表
     *
     * @param vhost   虚拟机名称
     * @param exchang 交换机名称
     * @author: liqingdian
     */
    public static String getBindingSources(String vhost, String exchang) {
        String format = AdminUtils.format("/api/exchanges/%s/%s/bindings/source", vhost, exchang);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 绑定目标列表
     *
     * @param vhost   虚拟机名称
     * @param exchang 交换机名称
     * @author: liqingdian
     */
    public static String getBindingDestinations(String vhost, String exchang) {
        String format = AdminUtils.format("/api/exchanges/%s/%s/bindings/destination", vhost, exchang);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 获取队列列表
     *
     * @author: liqingdian
     */
    public static String getQueues() {
        return AdminUtils.reqeust("/api/queues", HttpMethod.GET, null, null);
    }

    /**
     * 获取队列列表
     *
     * @param vhost 虚拟机名称
     * @author: liqingdian
     */
    public static String getQueues(String vhost) {
        String format = AdminUtils.format("/api/queues/%s", vhost);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 获取队列
     *
     * @param vhost 虚拟机名称
     * @param queue 队列名称
     * @author: liqingdian
     */
    public static String getQueue(String vhost, String queue) {
        String format = AdminUtils.format("/api/queues/%s/%s", vhost, queue);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 获取队列绑定列表
     *
     * @param vhost 虚拟机名称
     * @param queue 队列名称
     * @author: liqingdian
     */
    public static String getQueueBindings(String vhost, String queue) {
        String format = AdminUtils.format("/api/queues/%s/%s/bindings", vhost, queue);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 获取绑定列表
     *
     * @author: liqingdian
     */
    public static String getBindings() {
        return AdminUtils.reqeust("/api/bindings", HttpMethod.GET, null, null);
    }

    /**
     * 获取绑定列表
     *
     * @param vhost 虚拟机名称
     * @author: liqingdian
     */
    public static String getBindings(String vhost) {
        String format = AdminUtils.format("/api/bindings/%s", vhost);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 获取绑定列表
     *
     * @param vhost 虚拟机名称
     * @author: liqingdian
     */
    public static String getBindings(String vhost, String exchange, String queue) {
        String format = AdminUtils.format("/api/bindings/%s/e/%s/q/%s", vhost, exchange, queue);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 获取虚拟机列表
     *
     * @author: liqingdian
     */
    public static String getVhosts() {
        return AdminUtils.reqeust("/api/vhosts", HttpMethod.GET, null, null);
    }

    /**
     * 获取vhost列表
     *
     * @param vhost 虚拟机名称
     * @author: liqingdian
     */
    public static String getVhost(String vhost) {
        String format = AdminUtils.format("/api/vhosts/%s", vhost);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 启用/禁用tracing
     *
     * @param vhost   虚拟机名称
     * @param tracing 启用/禁用
     * @author: liqingdian
     */
    public static void saveVhost(String vhost, boolean tracing) {
        String format = AdminUtils.format("/api/vhosts/%s", vhost);
        JSONObject param = new JSONObject();
        param.put("tracing", tracing);
        AdminUtils.reqeust(format, HttpMethod.PUT, param.toJSONString(), null);
    }

    /**
     * 删除vhost
     *
     * @param vhost 虚拟机名称
     * @author: liqingdian
     */
    public static void deleteVhost(String vhost) {
        String format = AdminUtils.format("/api/vhosts/%s", vhost);
        AdminUtils.reqeust(format, HttpMethod.DELETE, null, null);
    }

    /**
     * 获取vhost列表
     *
     * @param vhost 虚拟机名称
     * @author: liqingdian
     */
    public static String getVhostPermissions(String vhost) {
        String format = AdminUtils.format("/api/vhosts/%s/permissions", vhost);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 获取用户列表
     *
     * @author: liqingdian
     */
    public static String getUsers() {
        return AdminUtils.reqeust("/api/users", HttpMethod.GET, null, null);
    }

    /**
     * 获取用户
     *
     * @param username 账号
     * @author: liqingdian
     */
    public static String getUser(String username) {
        String format = AdminUtils.format("/api/users/%s", username);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 保存用户
     *
     * @param username 账号
     * @param password 密码
     * @param role     角色
     * @author: liqingdian
     */
    public static String saveUser(String username, String password, Role role) {
        String format = AdminUtils.format("/api/users/%s", username);
        JSONObject param = new JSONObject();
        param.put("password", password);
        if (role == null) {
            param.put("tags", Role.IMPERSONATOR.name().toLowerCase());
        } else {
            param.put("tags", role.name().toLowerCase());
        }
        return AdminUtils.reqeust(format, HttpMethod.PUT, param.toJSONString(), null);
    }

    /**
     * 删除用户
     *
     * @param username 账号
     * @author: liqingdian
     */
    public static void deleteUser(String username) {
        String format = AdminUtils.format("/api/users/%s", username);
        AdminUtils.reqeust(format, HttpMethod.DELETE, null, null);
    }

    /**
     * 获取用户权限列表
     *
     * @param username 账号
     * @author: liqingdian
     */
    public static String getUserPermissions(String username) {
        String format = AdminUtils.format("/api/users/%s/permissions", username);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 获取用户权限列表
     *
     * @author: liqingdian
     */
    public static String getPermissions() {
        return AdminUtils.reqeust("/api/permissions", HttpMethod.GET, null, null);
    }

    /**
     * 获取用户权限
     *
     * @author: liqingdian
     */
    public static String getUserPermissions(String vhost, String username) {
        String format = AdminUtils.format("/api/permissions/%s/%s", vhost, username);
        return AdminUtils.reqeust(format, HttpMethod.GET, null, null);
    }

    /**
     * 保存权限
     *
     * @param vhost     虚拟机
     * @param username  账号
     * @param configure 配置权限
     * @param write     写权限
     * @param read      读权限
     * @author: liqingdian
     */
    public static String savePermissions(String vhost, String username, String configure, String write, String read) {
        String format = AdminUtils.format("/api/permissions/%s/%s", vhost, username);
        JSONObject param = new JSONObject();
        param.put("configure", StringUtils.isBlank(configure) ? "^$" : configure);
        param.put("write", StringUtils.isBlank(write) ? "^$" : write);
        param.put("read", StringUtils.isBlank(read) ? "^$" : read);
        return AdminUtils.reqeust(format, HttpMethod.PUT, param.toJSONString(), null);
    }

    /**
     * 测试
     *
     * @param vhost 虚拟机
     * @author: liqingdian
     */
    public static Boolean alivenessTest(String vhost) {
        String format = AdminUtils.format("/api/aliveness-test/%s", vhost);
        String reqeust = AdminUtils.reqeust(format, HttpMethod.GET, null, null);
        JSONObject object = JSON.parseObject(reqeust, JSONObject.class);
        return StringUtils.equalsIgnoreCase("ok", object.getString("status"));
    }
}